Skip to content

[libc++] Optimize multi{map,set}::insert(InputIterator, InputIterator) #152691

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

philnik777
Copy link
Contributor

@philnik777 philnik777 commented Aug 8, 2025

---------------------------------------------------------------------------------------------------------------------------------
Benchmark                                                                                                      old            new
---------------------------------------------------------------------------------------------------------------------------------
std::multimap<int, int>::ctor(iterator, iterator) (unsorted sequence)/0                                    15.0 ns        14.9 ns
std::multimap<int, int>::ctor(iterator, iterator) (unsorted sequence)/32                                    499 ns         453 ns
std::multimap<int, int>::ctor(iterator, iterator) (unsorted sequence)/1024                                48042 ns       45413 ns
std::multimap<int, int>::ctor(iterator, iterator) (unsorted sequence)/8192                               720794 ns      699102 ns
std::multimap<int, int>::ctor(iterator, iterator) (sorted sequence)/0                                      14.9 ns        15.0 ns
std::multimap<int, int>::ctor(iterator, iterator) (sorted sequence)/32                                      469 ns         312 ns
std::multimap<int, int>::ctor(iterator, iterator) (sorted sequence)/1024                                  24870 ns       12535 ns
std::multimap<int, int>::ctor(iterator, iterator) (sorted sequence)/8192                                 320474 ns       98615 ns
std::multimap<int, int>::insert(iterator, iterator) (all new keys)/0                                        455 ns         462 ns
std::multimap<int, int>::insert(iterator, iterator) (all new keys)/32                                      1069 ns        1024 ns
std::multimap<int, int>::insert(iterator, iterator) (all new keys)/1024                                   29448 ns       29650 ns
std::multimap<int, int>::insert(iterator, iterator) (all new keys)/8192                                  316087 ns      307652 ns
std::multimap<int, int>::insert(iterator, iterator) (half new keys)/0                                       456 ns         459 ns
std::multimap<int, int>::insert(iterator, iterator) (half new keys)/32                                      982 ns         926 ns
std::multimap<int, int>::insert(iterator, iterator) (half new keys)/1024                                  40963 ns       35205 ns
std::multimap<int, int>::insert(iterator, iterator) (half new keys)/8192                                 470584 ns      411297 ns
std::multimap<int, int>::insert(iterator, iterator) (product_iterator from same type)/0                     455 ns         455 ns
std::multimap<int, int>::insert(iterator, iterator) (product_iterator from same type)/32                    981 ns         908 ns
std::multimap<int, int>::insert(iterator, iterator) (product_iterator from same type)/1024                29573 ns       18495 ns
std::multimap<int, int>::insert(iterator, iterator) (product_iterator from same type)/8192               373138 ns      145490 ns
std::multimap<int, int>::insert(iterator, iterator) (product_iterator from zip_view)/0                      456 ns         457 ns
std::multimap<int, int>::insert(iterator, iterator) (product_iterator from zip_view)/32                    1001 ns         846 ns
std::multimap<int, int>::insert(iterator, iterator) (product_iterator from zip_view)/1024                 28886 ns       16380 ns
std::multimap<int, int>::insert(iterator, iterator) (product_iterator from zip_view)/8192                296621 ns      129634 ns
std::multimap<int, int>::erase(iterator, iterator) (erase half the container)/0                             461 ns         459 ns
std::multimap<int, int>::erase(iterator, iterator) (erase half the container)/32                            712 ns         721 ns
std::multimap<int, int>::erase(iterator, iterator) (erase half the container)/1024                         8683 ns        9032 ns
std::multimap<int, int>::erase(iterator, iterator) (erase half the container)/8192                        67316 ns       70256 ns
std::multimap<std::string, int>::ctor(iterator, iterator) (unsorted sequence)/0                            15.0 ns        17.0 ns
std::multimap<std::string, int>::ctor(iterator, iterator) (unsorted sequence)/32                           3133 ns        3079 ns
std::multimap<std::string, int>::ctor(iterator, iterator) (unsorted sequence)/1024                       189504 ns      190940 ns
std::multimap<std::string, int>::ctor(iterator, iterator) (unsorted sequence)/8192                      2578482 ns     2534645 ns
std::multimap<std::string, int>::ctor(iterator, iterator) (sorted sequence)/0                              15.2 ns        16.9 ns
std::multimap<std::string, int>::ctor(iterator, iterator) (sorted sequence)/32                             2690 ns        2189 ns
std::multimap<std::string, int>::ctor(iterator, iterator) (sorted sequence)/1024                          98889 ns       71348 ns
std::multimap<std::string, int>::ctor(iterator, iterator) (sorted sequence)/8192                        1274108 ns     1159230 ns
std::multimap<std::string, int>::insert(iterator, iterator) (all new keys)/0                                455 ns         456 ns
std::multimap<std::string, int>::insert(iterator, iterator) (all new keys)/32                              2007 ns        2076 ns
std::multimap<std::string, int>::insert(iterator, iterator) (all new keys)/1024                          160027 ns      156791 ns
std::multimap<std::string, int>::insert(iterator, iterator) (all new keys)/8192                         1540491 ns     1470501 ns
std::multimap<std::string, int>::insert(iterator, iterator) (half new keys)/0                               455 ns         457 ns
std::multimap<std::string, int>::insert(iterator, iterator) (half new keys)/32                             2003 ns        2063 ns
std::multimap<std::string, int>::insert(iterator, iterator) (half new keys)/1024                         163551 ns      167734 ns
std::multimap<std::string, int>::insert(iterator, iterator) (half new keys)/8192                        1592041 ns     1519982 ns
std::multimap<std::string, int>::insert(iterator, iterator) (product_iterator from same type)/0             452 ns         451 ns
std::multimap<std::string, int>::insert(iterator, iterator) (product_iterator from same type)/32           1647 ns        1553 ns
std::multimap<std::string, int>::insert(iterator, iterator) (product_iterator from same type)/1024       115961 ns       82969 ns
std::multimap<std::string, int>::insert(iterator, iterator) (product_iterator from same type)/8192       805502 ns      809357 ns
std::multimap<std::string, int>::insert(iterator, iterator) (product_iterator from zip_view)/0              456 ns         461 ns
std::multimap<std::string, int>::insert(iterator, iterator) (product_iterator from zip_view)/32            1599 ns        1569 ns
std::multimap<std::string, int>::insert(iterator, iterator) (product_iterator from zip_view)/1024        105381 ns       83137 ns
std::multimap<std::string, int>::insert(iterator, iterator) (product_iterator from zip_view)/8192        769359 ns      627449 ns
std::multimap<std::string, int>::erase(iterator, iterator) (erase half the container)/0                     454 ns         459 ns
std::multimap<std::string, int>::erase(iterator, iterator) (erase half the container)/32                    831 ns         826 ns
std::multimap<std::string, int>::erase(iterator, iterator) (erase half the container)/1024                22997 ns       22864 ns
std::multimap<std::string, int>::erase(iterator, iterator) (erase half the container)/8192               138008 ns      137066 ns
std::multiset<int>::ctor(iterator, iterator) (unsorted sequence)/0                                         15.0 ns        14.7 ns
std::multiset<int>::ctor(iterator, iterator) (unsorted sequence)/32                                         529 ns         425 ns
std::multiset<int>::ctor(iterator, iterator) (unsorted sequence)/1024                                     49784 ns       42171 ns
std::multiset<int>::ctor(iterator, iterator) (unsorted sequence)/8192                                    725039 ns      699864 ns
std::multiset<int>::ctor(iterator, iterator) (sorted sequence)/0                                           14.9 ns        14.8 ns
std::multiset<int>::ctor(iterator, iterator) (sorted sequence)/32                                           513 ns         354 ns
std::multiset<int>::ctor(iterator, iterator) (sorted sequence)/1024                                       25201 ns       12103 ns
std::multiset<int>::ctor(iterator, iterator) (sorted sequence)/8192                                      320001 ns       96768 ns
std::multiset<int>::insert(iterator, iterator) (all new keys)/0                                             452 ns         458 ns
std::multiset<int>::insert(iterator, iterator) (all new keys)/32                                           1048 ns        1026 ns
std::multiset<int>::insert(iterator, iterator) (all new keys)/1024                                        29217 ns       29509 ns
std::multiset<int>::insert(iterator, iterator) (all new keys)/8192                                       310906 ns      309259 ns
std::multiset<int>::insert(iterator, iterator) (half new keys)/0                                            462 ns         454 ns
std::multiset<int>::insert(iterator, iterator) (half new keys)/32                                           988 ns         908 ns
std::multiset<int>::insert(iterator, iterator) (half new keys)/1024                                       41545 ns       32431 ns
std::multiset<int>::insert(iterator, iterator) (half new keys)/8192                                      470042 ns      409618 ns
std::multiset<int>::erase(iterator, iterator) (erase half the container)/0                                  453 ns         453 ns
std::multiset<int>::erase(iterator, iterator) (erase half the container)/32                                 725 ns         717 ns
std::multiset<int>::erase(iterator, iterator) (erase half the container)/1024                              8734 ns        8848 ns
std::multiset<int>::erase(iterator, iterator) (erase half the container)/8192                             70278 ns       68465 ns
std::multiset<std::string>::ctor(iterator, iterator) (unsorted sequence)/0                                 15.4 ns        16.5 ns
std::multiset<std::string>::ctor(iterator, iterator) (unsorted sequence)/32                                3060 ns        1894 ns
std::multiset<std::string>::ctor(iterator, iterator) (unsorted sequence)/1024                            185144 ns      180302 ns
std::multiset<std::string>::ctor(iterator, iterator) (unsorted sequence)/8192                           2780124 ns     1883800 ns
std::multiset<std::string>::ctor(iterator, iterator) (sorted sequence)/0                                   15.6 ns        16.8 ns
std::multiset<std::string>::ctor(iterator, iterator) (sorted sequence)/32                                  2694 ns        2395 ns
std::multiset<std::string>::ctor(iterator, iterator) (sorted sequence)/1024                               85031 ns       80462 ns
std::multiset<std::string>::ctor(iterator, iterator) (sorted sequence)/8192                              854175 ns     1122345 ns
std::multiset<std::string>::insert(iterator, iterator) (all new keys)/0                                     459 ns         457 ns
std::multiset<std::string>::insert(iterator, iterator) (all new keys)/32                                   2120 ns        1999 ns
std::multiset<std::string>::insert(iterator, iterator) (all new keys)/1024                               161187 ns      141931 ns
std::multiset<std::string>::insert(iterator, iterator) (all new keys)/8192                              1416852 ns     1343619 ns
std::multiset<std::string>::insert(iterator, iterator) (half new keys)/0                                    459 ns         455 ns
std::multiset<std::string>::insert(iterator, iterator) (half new keys)/32                                  2022 ns        1927 ns
std::multiset<std::string>::insert(iterator, iterator) (half new keys)/1024                              169273 ns      152779 ns
std::multiset<std::string>::insert(iterator, iterator) (half new keys)/8192                             1640003 ns     1489332 ns
std::multiset<std::string>::erase(iterator, iterator) (erase half the container)/0                          453 ns         452 ns
std::multiset<std::string>::erase(iterator, iterator) (erase half the container)/32                         827 ns         806 ns
std::multiset<std::string>::erase(iterator, iterator) (erase half the container)/1024                     20004 ns       20002 ns
std::multiset<std::string>::erase(iterator, iterator) (erase half the container)/8192                    135073 ns      134105 ns

Copy link

github-actions bot commented Aug 8, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@philnik777 philnik777 changed the title [libc++] Optimize multi{map,set}::insert [libc++] Optimize multi{map,set}::insert(InputIterator, InputIterator) Aug 11, 2025
@philnik777 philnik777 force-pushed the optimize_tree_insert_range_multi branch 2 times, most recently from 2f03769 to 8bed2d3 Compare August 11, 2025 09:41
@@ -970,6 +970,36 @@ public:
__emplace_hint_multi(__p, std::move(__value));
}

template <class _InIter, class _Sent>
_LIBCPP_HIDE_FROM_ABI void __insert_range_multi(_InIter __first, _Sent __last) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's document what this function does and in particular what optimizations it performs (i.e. it is optimized for already-sorted inputs).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What sort of documentation do you want? I'm not sure there is much to say beyond "inserts the range [first, last) into *this", which I think is rather obvious.

@@ -970,6 +970,36 @@ public:
__emplace_hint_multi(__p, std::move(__value));
}

template <class _InIter, class _Sent>
_LIBCPP_HIDE_FROM_ABI void __insert_range_multi(_InIter __first, _Sent __last) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also suggest taking a tag here and handling the (very similar) unique/multi cases uniformly with a if constexpr. Then we can report improvements for map and set as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out that adding a unique implementation will be a lot more complex, since we have a lot more complex handling in __emplace_unique to avoid unnecessary allocations if the node already exists. I think it would be better to refactor __emplace_unique to make that simpler first and then tackle __insert_range_unique (or a merged version) separately.

@ldionne ldionne marked this pull request as ready for review August 12, 2025 15:05
@ldionne ldionne requested a review from a team as a code owner August 12, 2025 15:05
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Aug 12, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 12, 2025

@llvm/pr-subscribers-libcxx

Author: Nikolas Klauser (philnik777)

Changes
--------------------------------------------------------------------------------------------------------------------
Benchmark                                                                                        old             new
--------------------------------------------------------------------------------------------------------------------
std::multiset&lt;int&gt;::ctor(iterator, iterator) (unsorted sequence)/0                           14.8 ns         16.0 ns
std::multiset&lt;int&gt;::ctor(iterator, iterator) (unsorted sequence)/32                           513 ns          536 ns
std::multiset&lt;int&gt;::ctor(iterator, iterator) (unsorted sequence)/1024                       50523 ns        49099 ns
std::multiset&lt;int&gt;::ctor(iterator, iterator) (unsorted sequence)/8192                      712956 ns       773317 ns
std::multiset&lt;int&gt;::ctor(iterator, iterator) (sorted sequence)/0                             14.6 ns         16.0 ns
std::multiset&lt;int&gt;::ctor(iterator, iterator) (sorted sequence)/32                             505 ns          404 ns
std::multiset&lt;int&gt;::ctor(iterator, iterator) (sorted sequence)/1024                         24907 ns        14673 ns
std::multiset&lt;int&gt;::ctor(iterator, iterator) (sorted sequence)/8192                        317355 ns       115852 ns
std::multiset&lt;int&gt;::insert(iterator, iterator) (all new keys)/0                               449 ns          458 ns
std::multiset&lt;int&gt;::insert(iterator, iterator) (all new keys)/32                             1036 ns          965 ns
std::multiset&lt;int&gt;::insert(iterator, iterator) (all new keys)/1024                          29504 ns        20190 ns
std::multiset&lt;int&gt;::insert(iterator, iterator) (all new keys)/8192                         313616 ns       150854 ns
std::multiset&lt;int&gt;::insert(iterator, iterator) (half new keys)/0                              456 ns          459 ns
std::multiset&lt;int&gt;::insert(iterator, iterator) (half new keys)/32                            1013 ns          971 ns
std::multiset&lt;int&gt;::insert(iterator, iterator) (half new keys)/1024                         41227 ns        24008 ns
std::multiset&lt;int&gt;::insert(iterator, iterator) (half new keys)/8192                        478994 ns       343475 ns
std::multiset&lt;int&gt;::erase(iterator, iterator) (erase half the container)/0                    459 ns          456 ns
std::multiset&lt;int&gt;::erase(iterator, iterator) (erase half the container)/32                   732 ns          720 ns
std::multiset&lt;int&gt;::erase(iterator, iterator) (erase half the container)/1024               10027 ns         8581 ns
std::multiset&lt;int&gt;::erase(iterator, iterator) (erase half the container)/8192               74175 ns        65850 ns
std::multiset&lt;std::string&gt;::ctor(iterator, iterator) (unsorted sequence)/0                   15.2 ns         17.1 ns
std::multiset&lt;std::string&gt;::ctor(iterator, iterator) (unsorted sequence)/32                  3093 ns         3015 ns
std::multiset&lt;std::string&gt;::ctor(iterator, iterator) (unsorted sequence)/1024              164606 ns       178851 ns
std::multiset&lt;std::string&gt;::ctor(iterator, iterator) (unsorted sequence)/8192             2239105 ns      2458905 ns
std::multiset&lt;std::string&gt;::ctor(iterator, iterator) (sorted sequence)/0                     15.1 ns         16.9 ns
std::multiset&lt;std::string&gt;::ctor(iterator, iterator) (sorted sequence)/32                    1293 ns         2497 ns
std::multiset&lt;std::string&gt;::ctor(iterator, iterator) (sorted sequence)/1024                 94936 ns        68618 ns
std::multiset&lt;std::string&gt;::ctor(iterator, iterator) (sorted sequence)/8192               1252379 ns      1136337 ns
std::multiset&lt;std::string&gt;::insert(iterator, iterator) (all new keys)/0                       456 ns          463 ns
std::multiset&lt;std::string&gt;::insert(iterator, iterator) (all new keys)/32                     2024 ns         1807 ns
std::multiset&lt;std::string&gt;::insert(iterator, iterator) (all new keys)/1024                 159767 ns       105691 ns
std::multiset&lt;std::string&gt;::insert(iterator, iterator) (all new keys)/8192                1424748 ns       762697 ns
std::multiset&lt;std::string&gt;::insert(iterator, iterator) (half new keys)/0                      461 ns          458 ns
std::multiset&lt;std::string&gt;::insert(iterator, iterator) (half new keys)/32                    2013 ns         2040 ns
std::multiset&lt;std::string&gt;::insert(iterator, iterator) (half new keys)/1024                166583 ns       141373 ns
std::multiset&lt;std::string&gt;::insert(iterator, iterator) (half new keys)/8192               1566078 ns      1358940 ns
std::multiset&lt;std::string&gt;::erase(iterator, iterator) (erase half the container)/0            454 ns          454 ns
std::multiset&lt;std::string&gt;::erase(iterator, iterator) (erase half the container)/32           841 ns          813 ns
std::multiset&lt;std::string&gt;::erase(iterator, iterator) (erase half the container)/1024       18878 ns        20836 ns
std::multiset&lt;std::string&gt;::erase(iterator, iterator) (erase half the container)/8192      136735 ns       135926 ns

Full diff: https://github.com/llvm/llvm-project/pull/152691.diff

3 Files Affected:

  • (modified) libcxx/include/__tree (+30)
  • (modified) libcxx/include/map (+1)
  • (modified) libcxx/include/set (+4-7)
diff --git a/libcxx/include/__tree b/libcxx/include/__tree
index 6dadd0915c984..5baadca719ac4 100644
--- a/libcxx/include/__tree
+++ b/libcxx/include/__tree
@@ -970,6 +970,36 @@ public:
     __emplace_hint_multi(__p, std::move(__value));
   }
 
+  template <class _InIter, class _Sent>
+  _LIBCPP_HIDE_FROM_ABI void __insert_range_multi(_InIter __first, _Sent __last) {
+    if (__first == __last)
+      return;
+
+    if (__root() == nullptr) { // Make sure we always have a root node
+      __node_holder __node = __construct_node(*__first);
+      __insert_node_at(__end_node(), __end_node()->__left_, static_cast<__node_base_pointer>(__node.release()));
+      ++__first;
+    }
+
+    auto __max_node = static_cast<__node_pointer>(std::__tree_max(static_cast<__node_base_pointer>(__root())));
+    __node_pointer __last_insertion = __root();
+
+    for (; __first != __last; ++__first) {
+      __node_holder __node = __construct_node(*__first);
+      if (!value_comp()(__node->__get_value(), __max_node->__get_value())) { // __node >= __max_val
+        __insert_node_at(static_cast<__end_node_pointer>(__max_node),
+                         __max_node->__right_,
+                         static_cast<__node_base_pointer>(__node.get()));
+        __max_node = __node.release();
+      } else {
+        __end_node_pointer __parent;
+        __node_base_pointer& __child = __find_leaf(++iterator(__last_insertion), __parent, __node->__value_);
+        __last_insertion             = __node.get();
+        __insert_node_at(__parent, __child, static_cast<__node_base_pointer>(__node.release()));
+      }
+    }
+  }
+
   _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> __node_assign_unique(const value_type& __v, __node_pointer __dest);
 
   _LIBCPP_HIDE_FROM_ABI iterator __node_insert_multi(__node_pointer __nd);
diff --git a/libcxx/include/map b/libcxx/include/map
index 9f98abef9afe0..a84b5592244fa 100644
--- a/libcxx/include/map
+++ b/libcxx/include/map
@@ -593,6 +593,7 @@ erase_if(multimap<Key, T, Compare, Allocator>& c, Predicate pred);  // C++20
 #  include <__memory/unique_ptr.h>
 #  include <__memory_resource/polymorphic_allocator.h>
 #  include <__node_handle>
+#  include <__ranges/access.h>
 #  include <__ranges/concepts.h>
 #  include <__ranges/container_compatible_range.h>
 #  include <__ranges/from_range.h>
diff --git a/libcxx/include/set b/libcxx/include/set
index c77345bc5dc1f..18488b56e7dd2 100644
--- a/libcxx/include/set
+++ b/libcxx/include/set
@@ -530,6 +530,7 @@ erase_if(multiset<Key, Compare, Allocator>& c, Predicate pred);  // C++20
 #  include <__memory/allocator_traits.h>
 #  include <__memory_resource/polymorphic_allocator.h>
 #  include <__node_handle>
+#  include <__ranges/access.h>
 #  include <__ranges/concepts.h>
 #  include <__ranges/container_compatible_range.h>
 #  include <__ranges/from_range.h>
@@ -1205,18 +1206,14 @@ public:
   }
 
   template <class _InputIterator>
-  _LIBCPP_HIDE_FROM_ABI void insert(_InputIterator __f, _InputIterator __l) {
-    for (const_iterator __e = cend(); __f != __l; ++__f)
-      __tree_.__emplace_hint_multi(__e, *__f);
+  _LIBCPP_HIDE_FROM_ABI void insert(_InputIterator __first, _InputIterator __last) {
+    __tree_.__insert_range_multi(__first, __last);
   }
 
 #  if _LIBCPP_STD_VER >= 23
   template <_ContainerCompatibleRange<value_type> _Range>
   _LIBCPP_HIDE_FROM_ABI void insert_range(_Range&& __range) {
-    const_iterator __end = cend();
-    for (auto&& __element : __range) {
-      __tree_.__emplace_hint_multi(__end, std::forward<decltype(__element)>(__element));
-    }
+    __tree_.__insert_range_multi(ranges::begin(__range), ranges::end(__range));
   }
 #  endif
 

@philnik777 philnik777 force-pushed the optimize_tree_insert_range_multi branch 2 times, most recently from 9b9dbda to 0e5d4f1 Compare August 14, 2025 09:31
@philnik777 philnik777 force-pushed the optimize_tree_insert_range_multi branch from 0e5d4f1 to 691222a Compare August 15, 2025 08:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. performance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants